<!DOCTYPE html>
<html>

  <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <title>安卓自定义View进阶-缩放手势检测(ScaleGestureDecetor)</title>
  <meta name="description" content="Android 手势检测，主要是 ScaleGestureDecetor 相关内容的用法和注意事项，本文依旧属于事件处理这一体系，部分内容会涉及到之前文章提及过的知识点，如果你没看过之前的文章，可以到自定义View系列来查看这些内容。">
  <meta name="author" content="GcsSloop">
  <meta name="keywords" content="ScaleGestureDecetor, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  <meta name="关键字" content="ScaleGestureDecetor, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  

  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="安卓自定义View进阶-缩放手势检测(ScaleGestureDecetor)">
  <meta name="twitter:description" content="Android 手势检测，主要是 ScaleGestureDecetor 相关内容的用法和注意事项，本文依旧属于事件处理这一体系，部分内容会涉及到之前文章提及过的知识点，如果你没看过之前的文章，可以到自定义View系列来查看这些内容。">
  <meta name="twitter:keywords" content="ScaleGestureDecetor, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  
  <meta property="og:type" content="article">
  <meta property="og:title" content="安卓自定义View进阶-缩放手势检测(ScaleGestureDecetor)">
  <meta property="og:description" content="Android 手势检测，主要是 ScaleGestureDecetor 相关内容的用法和注意事项，本文依旧属于事件处理这一体系，部分内容会涉及到之前文章提及过的知识点，如果你没看过之前的文章，可以到自定义View系列来查看这些内容。">
  <meta name="og:keywords" content="ScaleGestureDecetor, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">

  <meta name="theme-color" content="#343434">
  
  <link rel="icon" type="image/png" href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" />
  <link href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" rel="shortcut icon" type="image/png">
  
  <link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/css/main.css">

  <link rel="canonical" href="http://www.gcssloop.com/customview/scalegesturedetector">
  <link rel="alternate" type="application/rss+xml" title="GcsSloop" href="http://www.gcssloop.com/feed.xml">
  
  <meta name="google-site-verification" content="Z_g58PkzRAyBMxkqrcDdWrTBK8oOWM-7rUHauhLNF2E" />
  <meta name="baidu-site-verification" content="kUtTXCKaZs" />
  <meta name="baidu-site-verification" content="6DuDv3aaJX" />
  
  <!--阅读次数统计-->
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"> </script>
  
  <!--Fuck Weixin and Baidu-->
  <meta http-equiv="Cache-Control" content="no-transform">
  <meta http-equiv=”Cache-Control” content=”no-siteapp” />
  <meta name="applicable-device" content="pc,mobile">
  <meta name="HandheldFriendly" content="true"/>

  <!-- Google Ad -->
  <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <script>
    (adsbygoogle = window.adsbygoogle || []).push({
      google_ad_client: "ca-pub-2767831356529421",
      enable_page_level_ads: true
    });
  </script>

</head>


  <body>

    <span class="mobile btn-mobile-menu">
        <i class="fa fa-list btn-mobile-menu__icon"></i>
        <i class="fa fa-angle-up btn-mobile-close__icon hidden"></i>
    </span>
    
    <header class="panel-cover panel-cover--collapsed" style="background-image: url('/assets/siteinfo/background-cover.jpg')">
  <div class="panel-main">

    <div class="panel-main__inner panel-inverted">
    <div class="panel-main__content">

        <a href="/#blog" title="前往 GcsSloop 的主页" class="blog-button"><img src="/assets/siteinfo/avatar.jpg" width="80" alt="GcsSloop logo" class="panel-cover__logo logo" /></a>
        <h1 class="panel-cover__title panel-title"><a href="/#blog" title="link to homepage for GcsSloop" class="blog-button">GcsSloop</a></h1>

        
        <span class="panel-cover__subtitle panel-subtitle">Just do IT later.</span>
        
        <hr class="panel-cover__divider" />
        <p class="panel-cover__description">嗨，我是 GcsSloop，一名来自2.5次元的魔法师，Android自定义View系列文章作者，非著名程序员。</p>
        <hr class="panel-cover__divider panel-cover__divider--secondary" />
        
        
        <p class="panel-cover__description">欢迎来到我的魔法世界!</p>
        
        
        <div class="navigation-wrapper">
          <div>
            <nav class="cover-navigation cover-navigation--primary">
              <ul class="navigation">
                <li class="navigation__item"><a href="/#blog" title="访问博客" class="blog-button">博客</a></li>
                
                  
                    <li class="navigation__item"><a href="https://github.com/GcsSloop" target="_blank" title="GcsSloop's GitHub">GitHub</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/timeline" title="博客目录">目录</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="https://xiaozhuanlan.com/u/GcsSloop" target="_blank" title="小专栏">专栏</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/category/customview" title="自定义View教程目录">自定义控件</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/friends" title="友链">友链</a></li>
                  
                  
                
              </ul>
            </nav>
          </div>
          
          <div><nav class="cover-navigation navigation--social">
  <ul class="navigation">

  
  <!-- Weibo -->
  <li class="navigation__item">
    <a href="http://weibo.com/GcsSloop" title="@GcsSloop 的微博" target="_blank">
      <i class='social fa fa-weibo'></i>
      <span class="label">Weibo</span>
    </a>
  </li>
  

  
  <!-- Github -->
  <li class="navigation__item">
    <a href="https://github.com/GcsSloop" title="@GcsSloop 的 Github" target="_blank">
      <i class='social fa fa-github'></i>
      <span class="label">Github</span>
    </a>
  </li>
  
  
  
  <!-- Twitter -->
  <li class="navigation__item">
    <a href="http://twitter.com/GcsSloop" title="@GcsSloop" target="_blank">
      <i class='social fa fa-twitter'></i>
      <span class="label">Twitter</span>
    </a>
  </li>
  

    

  

  
  <!-- RSS -->
  <li class="navigation__item">
    <a href="/feed.xml" rel="author" title="RSS" target="_blank">
      <i class='social fa fa-rss'></i>
      <span class="label">RSS</span>
    </a>
  </li>
  

  
  <!-- Email -->
  <li class="navigation__item">
    <a href="mailto:GcsSloop@gmail.com" title="发邮件给我">
      <i class='social fa fa-envelope'></i>
      <span class="label">Email</span>
    </a>
  </li>
  

  
  <!-- Copyright -->
  <li class="navigation__item">
    <a href="http://choosealicense.online" title="选择版权"  target="_blank">
      <i class="social fa fa-copyright"></i>
      <span class="label">版权</span>
    </a>
  </li>
  
  
  </ul>
</nav>
</div>
        </div>
      </div>
    </div>
    
    
    <div class="panel-cover--overlay cover-slate"></div>
    
  </div>
</header>


    <div class="content-wrapper">
        <div class="content-wrapper__inner">
            <article class="post-container post-container--single" itemscope itemtype="http://schema.org/BlogPosting">
  <header class="post-header">
    <div class="post-meta" style="font-size:.8em">
      <time datetime="2018-08-09 00:00:00 +0800" itemprop="datePublished" class="post-meta__date date">2018-08-09</time> &#8226; <span class="post-meta__tags tags">Android,ScaleGestureDecetor</span> &#8226; View <span id="busuanzi_value_page_pv"></span> times.
</span>
    </div>
    <h1 class="post-title">安卓自定义View进阶-缩放手势检测(ScaleGestureDecetor)</h1>
  </header>

  <section class="post">
    <h2 id="0-前言">0. 前言</h2>

<p>Android 缩放手势检测，ScaleGestureDetector 相关内容的用法和注意事项，本文依旧属于事件处理这一体系，在大多数的情况下，缩放手势都不是单独存在的，需要配合其它的手势来使用，所以推荐配合 <a href="http://www.gcssloop.com/customview/gestruedector">手势检测(GestureDetector)</a> 一起观看。如果是用在自定义的控件上，则需要配合 Matrix 相关内容使用起来可能会更加方便，如果对 Matrix 不太熟悉也可以看本系列文章之前的内容来补充一下相关知识。如果你没看过之前的文章，可以到 <a href="http://www.gcssloop.com/customview/CustomViewIndex">自定义 View 系列</a> 来查看这些内容。</p>

<p>缩放手势对于大部分 Android 工程师来说，需要用到的机会比较少，它最常见于以下的一些应用场景中，例如：图片浏览，图片编辑(贴图效果)、网页缩放、地图、文本阅读(通过缩放手势调整文字大小)等。应用场景相对比较狭窄，不过肯定也会有一些用武之地，它可以实现如下的效果：</p>

<p><img src="/assets/customview/scale-gesture-decetor/demo.gif" alt="demo-3656658" /></p>

<h2 id="2缩放手势检测scalegesturedetector">2.缩放手势检测(ScaleGestureDetector)</h2>

<p>缩放手势检测同样是官方提供的手势检测工具，它的使用方式的 GentureDetector 类似，也是通过 Listener 进行监听用户的操作手势，它是对缩放手势进行了一次封装， 可以方便用户快速的完成缩放相关功能的开发。缩放手势相对比较简单，网络上也能查到不少非官方实现的缩放手势计算方案，但部分非官方的方案确实有所局限，例如只支持两个手指的计算，在出现超过两个手指时，只计算了前两个手指的移动，这样显然是不合理的。而官方的这种实现方案轻松的应对了多个手指的情况，下面我们就来看看它是如何实现的吧。</p>

<h3 id="21-构造方法">2.1 构造方法</h3>

<p>它有两个构造方法，和 GestureDetector 类似，如下所示：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="n">ScaleGestureDetector</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">ScaleGestureDetector</span><span class="o">.</span><span class="na">OnScaleGestureListener</span> <span class="n">listener</span><span class="o">)</span>

<span class="n">ScaleGestureDetector</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">ScaleGestureDetector</span><span class="o">.</span><span class="na">OnScaleGestureListener</span> <span class="n">listener</span><span class="o">,</span> <span class="n">Handler</span> <span class="n">handler</span><span class="o">)</span>
</code></pre>
</div>

<h3 id="22-手势监听器">2.2 手势监听器</h3>

<p>它只有两个监听器，但严格来说，这两个监听器是同一个，只不过一个是接口，另一个是空实现而已。</p>

<table>
  <thead>
    <tr>
      <th>监听器</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>OnScaleGestureListener</td>
      <td>缩放手势检测器。</td>
    </tr>
    <tr>
      <td>SimpleOnScaleGestureListener</td>
      <td>缩放手势检测器的空实现。</td>
    </tr>
  </tbody>
</table>

<h4 id="onscalegesturelistener">OnScaleGestureListener</h4>

<p>缩放手势监听器有 3 个方法：</p>

<table>
  <thead>
    <tr>
      <th>方法</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>boolean <a href="https://developer.android.com/reference/android/view/ScaleGestureDetector.OnScaleGestureListener.html#onScaleBegin(android.view.ScaleGestureDetector)">onScaleBegin</a>(<a href="https://developer.android.com/reference/android/view/ScaleGestureDetector.html">ScaleGestureDetector</a> detector)</td>
      <td>缩放手势开始，当两个手指放在屏幕上的时候会调用该方法(只调用一次)。如果返回 false 则表示不使用当前这次缩放手势。</td>
    </tr>
    <tr>
      <td>boolean <a href="https://developer.android.com/reference/android/view/ScaleGestureDetector.OnScaleGestureListener.html#onScale(android.view.ScaleGestureDetector)">onScale</a>(<a href="https://developer.android.com/reference/android/view/ScaleGestureDetector.html">ScaleGestureDetector</a> detector)</td>
      <td>缩放被触发(会调用0次或者多次)，如果返回 true 则表示当前缩放事件已经被处理，检测器会重新积累缩放因子，返回 false 则会继续积累缩放因子。</td>
    </tr>
    <tr>
      <td>void <a href="https://developer.android.com/reference/android/view/ScaleGestureDetector.OnScaleGestureListener.html#onScaleEnd(android.view.ScaleGestureDetector)">onScaleEnd</a>(<a href="https://developer.android.com/reference/android/view/ScaleGestureDetector.html">ScaleGestureDetector</a> detector)</td>
      <td>缩放手势结束。</td>
    </tr>
  </tbody>
</table>

<h3 id="23-简单示例">2.3 简单示例</h3>

<p>这是使用 ScaleGestureDetector 的一个极简用例，当然，它没有实现任何功能，只是用日志的方式输出了几个我们比较关心的参数而已。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ScaleGestureDemoView</span> <span class="kd">extends</span> <span class="n">View</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"ScaleGestureDemoView"</span><span class="o">;</span>

    <span class="kd">private</span> <span class="n">ScaleGestureDetector</span> <span class="n">mScaleGestureDetector</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">ScaleGestureDemoView</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">ScaleGestureDemoView</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="n">AttributeSet</span> <span class="n">attrs</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">attrs</span><span class="o">);</span>
        <span class="n">initScaleGestureDetector</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">initScaleGestureDetector</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">mScaleGestureDetector</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ScaleGestureDetector</span><span class="o">(</span><span class="n">getContext</span><span class="o">(),</span> <span class="k">new</span> <span class="n">ScaleGestureDetector</span><span class="o">.</span><span class="na">SimpleOnScaleGestureListener</span><span class="o">()</span> <span class="o">{</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onScaleBegin</span><span class="o">(</span><span class="n">ScaleGestureDetector</span> <span class="n">detector</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
            <span class="o">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onScale</span><span class="o">(</span><span class="n">ScaleGestureDetector</span> <span class="n">detector</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"focusX = "</span> <span class="o">+</span> <span class="n">detector</span><span class="o">.</span><span class="na">getFocusX</span><span class="o">());</span>       <span class="c1">// 缩放中心，x坐标</span>
                <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"focusY = "</span> <span class="o">+</span> <span class="n">detector</span><span class="o">.</span><span class="na">getFocusY</span><span class="o">());</span>       <span class="c1">// 缩放中心y坐标</span>
                <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"scale = "</span> <span class="o">+</span> <span class="n">detector</span><span class="o">.</span><span class="na">getScaleFactor</span><span class="o">());</span>   <span class="c1">// 缩放因子</span>
                <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
            <span class="o">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onScaleEnd</span><span class="o">(</span><span class="n">ScaleGestureDetector</span> <span class="n">detector</span><span class="o">)</span> <span class="o">{</span>
            <span class="o">}</span>
        <span class="o">});</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">mScaleGestureDetector</span><span class="o">.</span><span class="na">onTouchEvent</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<h2 id="3-基本原理">3. 基本原理</h2>

<p>由于缩放手势检测使用起来非常简单，没有什么复杂的内容，不仅如此，它的实现也非常简单，下面我就带大家简单分析一下它的基本原理。<strong>在缩放手势中我们其实主要关心的只有两个参数而已，一个是缩放的中心点，另一个就是缩放比例了。</strong> 下面我们就看看这两个参数是如何计算出来的.</p>

<h3 id="31-计算缩放的中心点焦点">3.1 计算缩放的中心点(焦点)</h3>

<p>如果只有两个手指的话，缩放的中心点自然是非常容易计算的，那就是两个手指坐标的中点，但是如果有多个手指该如何计算缩放的中心点呢？</p>

<p><strong>计算中心点的原理其实也非常简单，那就是将所有的坐标都加起来，然后除以数量即可。</strong></p>

<p>这是一个简单的数学原理，并不复杂，如果有不理解的，自己尝试计算一下也就能明白了。不过在实际运用中还是需要注意一下的， 用户的手指数量可能并不是固定的，用户可能随时抬起来或者按下手指，ScaleGestureDetector 中是这样实现的：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">anchoredScaleCancelled</span> <span class="o">=</span>
            <span class="n">mAnchoredScaleMode</span> <span class="o">==</span> <span class="n">ANCHORED_SCALE_MODE_STYLUS</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">isStylusButtonDown</span><span class="o">;</span>
    <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">streamComplete</span> <span class="o">=</span> <span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span> <span class="o">||</span>
            <span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_CANCEL</span> <span class="o">||</span> <span class="n">anchoredScaleCancelled</span><span class="o">;</span>

    <span class="c1">// 注意这里</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span> <span class="o">||</span> <span class="n">streamComplete</span><span class="o">)</span> <span class="o">{</span>
		<span class="c1">//重置侦听器正在进行的任何缩放。</span>
        <span class="c1">//如果是ACTION_DOWN，我们正在开始一个新的事件流。</span>
        <span class="c1">//这意味着应用程序可能没有给我们所有的事件(事件被上层直接拦截了)。</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">mInProgress</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">mListener</span><span class="o">.</span><span class="na">onScaleEnd</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
            <span class="n">mInProgress</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
            <span class="n">mInitialSpan</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
            <span class="n">mAnchoredScaleMode</span> <span class="o">=</span> <span class="n">ANCHORED_SCALE_MODE_NONE</span><span class="o">;</span>
        <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">inAnchoredScaleMode</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">streamComplete</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">mInProgress</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
            <span class="n">mInitialSpan</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
            <span class="n">mAnchoredScaleMode</span> <span class="o">=</span> <span class="n">ANCHORED_SCALE_MODE_NONE</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="k">if</span> <span class="o">(</span><span class="n">streamComplete</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>
</code></pre>
</div>

<p>可以看到，当触发 down 或者触发 up，cancel 时，如果之前处于缩放计算的状态，会将其状态重置， 并调用 onScaleEnd 方法。</p>

<blockquote>
  <p>当然， 你可能注意到了 mAnchoredScaleMode 等内容，这些是对触控笔等外设的支持，对于大部分工程师来说，用到的机会比较少， 不管即可。</p>
</blockquote>

<p><strong>计算中心点：</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">final</span> <span class="kt">boolean</span> <span class="n">configChanged</span> <span class="o">=</span> <span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span> <span class="o">||</span>
        <span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_UP</span> <span class="o">||</span>
        <span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_DOWN</span> <span class="o">||</span> <span class="n">anchoredScaleCancelled</span><span class="o">;</span>

<span class="c1">// 注意这里</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">pointerUp</span> <span class="o">=</span> <span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_UP</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">skipIndex</span> <span class="o">=</span> <span class="n">pointerUp</span> <span class="o">?</span> <span class="n">event</span><span class="o">.</span><span class="na">getActionIndex</span><span class="o">()</span> <span class="o">:</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>

<span class="c1">// 确定焦点</span>
<span class="kt">float</span> <span class="n">sumX</span> <span class="o">=</span> <span class="mi">0</span><span class="o">,</span> <span class="n">sumY</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">div</span> <span class="o">=</span> <span class="n">pointerUp</span> <span class="o">?</span> <span class="n">count</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">:</span> <span class="n">count</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">focusX</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">focusY</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">inAnchoredScaleMode</span><span class="o">())</span> <span class="o">{</span>
    <span class="c1">// 在锚定比例模式下，焦点始终是双击或按钮按下时手势开始的位置</span>
    <span class="n">focusX</span> <span class="o">=</span> <span class="n">mAnchoredScaleStartX</span><span class="o">;</span>
    <span class="n">focusY</span> <span class="o">=</span> <span class="n">mAnchoredScaleStartY</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">()</span> <span class="o">&lt;</span> <span class="n">focusY</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">mEventBeforeOrAboveStartingGestureEvent</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
        <span class="n">mEventBeforeOrAboveStartingGestureEvent</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
	<span class="c1">// 注意这里， 最终计算得到焦点</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">skipIndex</span> <span class="o">==</span> <span class="n">i</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
        <span class="n">sumX</span> <span class="o">+=</span> <span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
        <span class="n">sumY</span> <span class="o">+=</span> <span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="n">focusX</span> <span class="o">=</span> <span class="n">sumX</span> <span class="o">/</span> <span class="n">div</span><span class="o">;</span>
    <span class="n">focusY</span> <span class="o">=</span> <span class="n">sumY</span> <span class="o">/</span> <span class="n">div</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<h3 id="32-计算缩放比例">3.2 计算缩放比例</h3>

<p><strong>计算缩放比例也很简单，就是计算各个手指到焦点的平均距离，在用户手指移动后用新的平均距离除以旧的平均距离，并以此计算得出缩放比例。</strong></p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// 计算到焦点的平均距离</span>
<span class="kt">float</span> <span class="n">devSumX</span> <span class="o">=</span> <span class="mi">0</span><span class="o">,</span> <span class="n">devSumY</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">skipIndex</span> <span class="o">==</span> <span class="n">i</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span>
    <span class="n">devSumX</span> <span class="o">+=</span> <span class="n">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getX</span><span class="o">(</span><span class="n">i</span><span class="o">)</span> <span class="o">-</span> <span class="n">focusX</span><span class="o">);</span>
    <span class="n">devSumY</span> <span class="o">+=</span> <span class="n">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getY</span><span class="o">(</span><span class="n">i</span><span class="o">)</span> <span class="o">-</span> <span class="n">focusY</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">devX</span> <span class="o">=</span> <span class="n">devSumX</span> <span class="o">/</span> <span class="n">div</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">devY</span> <span class="o">=</span> <span class="n">devSumY</span> <span class="o">/</span> <span class="n">div</span><span class="o">;</span>

<span class="c1">// 注意这里</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">spanX</span> <span class="o">=</span> <span class="n">devX</span> <span class="o">*</span> <span class="mi">2</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">spanY</span> <span class="o">=</span> <span class="n">devY</span> <span class="o">*</span> <span class="mi">2</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">span</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">inAnchoredScaleMode</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">span</span> <span class="o">=</span> <span class="n">spanY</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
    <span class="c1">// 相当于 sqrt(x*x + y*y)</span>
    <span class="n">span</span> <span class="o">=</span> <span class="o">(</span><span class="kt">float</span><span class="o">)</span> <span class="n">Math</span><span class="o">.</span><span class="na">hypot</span><span class="o">(</span><span class="n">spanX</span><span class="o">,</span> <span class="n">spanY</span><span class="o">);</span>
<span class="o">}</span>
</code></pre>
</div>

<p>当用户移动的距离超过一定数值(数值大小由系统定义)后，会触发 onScaleBegin 方法，如果用户在 onScaleBegin 方法里面返回了 true，表示接受事件后，就会重置缩放相关数值，并且开始积累缩放因子。</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="c1">// mSpanSlop 和 mMinSpan 都是从系统里面取得的预定义数值，该数值实际上影响的是缩放的灵敏度。</span>
<span class="c1">// 不过该参数并没有提供设置的方法，如果对灵敏度不满意的话，和通过直接之际复制一个 ScaleGestureDetector 到项目中， 并且修改其中的数值。</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">minSpan</span> <span class="o">=</span> <span class="n">inAnchoredScaleMode</span><span class="o">()</span> <span class="o">?</span> <span class="n">mSpanSlop</span> <span class="o">:</span> <span class="n">mMinSpan</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mInProgress</span> <span class="o">&amp;&amp;</span> <span class="n">span</span> <span class="o">&gt;=</span>  <span class="n">minSpan</span> <span class="o">&amp;&amp;</span>
        <span class="o">(</span><span class="n">wasInProgress</span> <span class="o">||</span> <span class="n">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">span</span> <span class="o">-</span> <span class="n">mInitialSpan</span><span class="o">)</span> <span class="o">&gt;</span> <span class="n">mSpanSlop</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">mPrevSpanX</span> <span class="o">=</span> <span class="n">mCurrSpanX</span> <span class="o">=</span> <span class="n">spanX</span><span class="o">;</span>
    <span class="n">mPrevSpanY</span> <span class="o">=</span> <span class="n">mCurrSpanY</span> <span class="o">=</span> <span class="n">spanY</span><span class="o">;</span>
    <span class="n">mPrevSpan</span> <span class="o">=</span> <span class="n">mCurrSpan</span> <span class="o">=</span> <span class="n">span</span><span class="o">;</span>
    <span class="n">mPrevTime</span> <span class="o">=</span> <span class="n">mCurrTime</span><span class="o">;</span>
    <span class="n">mInProgress</span> <span class="o">=</span> <span class="n">mListener</span><span class="o">.</span><span class="na">onScaleBegin</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
</code></pre>
</div>

<p>通知用户缩放：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">action</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MOVE</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">mCurrSpanX</span> <span class="o">=</span> <span class="n">spanX</span><span class="o">;</span>
    <span class="n">mCurrSpanY</span> <span class="o">=</span> <span class="n">spanY</span><span class="o">;</span>
    <span class="n">mCurrSpan</span> <span class="o">=</span> <span class="n">span</span><span class="o">;</span>

    <span class="kt">boolean</span> <span class="n">updatePrev</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>

    <span class="k">if</span> <span class="o">(</span><span class="n">mInProgress</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 注意这里，用户的返回值决定了是否重新计算缩放因子</span>
        <span class="n">updatePrev</span> <span class="o">=</span> <span class="n">mListener</span><span class="o">.</span><span class="na">onScale</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// 如果用户返回了 true ，就会重新计算缩放因子</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">updatePrev</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">mPrevSpanX</span> <span class="o">=</span> <span class="n">mCurrSpanX</span><span class="o">;</span>
        <span class="n">mPrevSpanY</span> <span class="o">=</span> <span class="n">mCurrSpanY</span><span class="o">;</span>
        <span class="n">mPrevSpan</span> <span class="o">=</span> <span class="n">mCurrSpan</span><span class="o">;</span>
        <span class="n">mPrevTime</span> <span class="o">=</span> <span class="n">mCurrTime</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>

<h2 id="4-后记">4. 后记</h2>

<p>由于缩放手势检测确实比较简单，没什么好讲的内容，如果看了上面的内容依旧有疑惑的话，推荐去看一下源码，源码的逻辑也非常简洁明了，相信看完之后也就能够彻底理解它的原理了。</p>

<p>最后附上文章一开始展示所用图片的源代码，点击下载项目获取源代码。</p>

<p><a href="http://android.demo.gcssloop.com/ScaleGestureDemo.zip"><strong>下载项目</strong></a></p>

<h2 id="about-me">About Me</h2>

<h3 id="作者微博-gcssloop">作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a></h3>

<p><a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/gcs_banner.jpg?gcssloop" width="300" style="display:inline;" /></a></p>


    <hr>
  </section>
</article>

<!--广告-->

<!--
<div>
<a href="https://m.aliyun.com/act/team1111/?spm=5176.11533457.1089570.5.424777e3AF8WOJ&userCode=hn5smxtw#/" target="_blank"><img src="/assets/aliyun/1111-980-120.jpg" width="100%"></a>
</div>
-->
<!--捐赠晶石-->
<section class="contribute">
    <script type="text/javascript"> 
      function toggle() {
        var con = document.getElementById("contribute");
        if (con.style.display == "none") {
          con.style.display = "block";
        } else {
          con.style.display = "none";
        }
      }
    </script> 
    <blockquote style="background-color:#F5F5F5; padding: 10px 20px 20px 10px; margin:0px" >
      <h4> 如果你觉得我的文章对你有帮助的话，欢迎赞助一些服务器费用! </h4>
      <p></p>
      <a id=“btn-toggle-contribute” class="btn-contribute" onclick="toggle()" >¥ 点击赞助</a>
      <br>
      <div id="contribute" style="display:none;">
        <p align="center" >
        <img src="/assets/images/wechat.png" alt="微信">
        <img src="/assets/images/alipay.png" alt="支付宝">
        </p>
        <p align="left" >
          <b>感谢所有支持我的魔法师，所有支持过我的魔法师都可以通过微信(GcsSloop)联系我，获赠我的付费专栏！</b>
          <!--
          <a href="/contribute">点击这里查看捐赠者名单。</a>
          -->
        </p>
      </div>
    </blockquote>
</section>
<div>
  <h2>欢迎关注我的微信公众号</h2>
  <img src="/assets/images/banner.jpg" width="100%">
</div>

<!--阅读更多-->
<section class="read-more">
  
  
  <div class="read-more-item">
    <span class="read-more-item-dim">最近的文章</span>
    <h2 class="post-list__post-title post-title"><a href="/gebug/android-stream" title="link to 雕虫晓技(八) Android与数据流的斗争">雕虫晓技(八) Android与数据流的斗争</a></h2>
    <p class="excerpt">关于作者GcsSloop，一名 2.5 次元魔法师。微博 | GitHub | 博客本文并没有什么实质性的内容，只是自己工作和学习这段时间的一些经验杂谈，仅从自己的角度阐述一下自己观察到的一些内...&hellip;</p>
    <div class="post-list__meta">
      <time datetime="2018-08-12 00:00:00 +0800" class="post-list__meta--date date">2018-08-12</time> &#8226; <span class="post-list__meta--tags tags">GeBug</span>
      <br/><br/>
      <a style="float:none; margin:0 auto;" class="btn-border-small" href=/gebug/android-stream>继续阅读</a></div>
   </div>
   
   
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">更早的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/gebug/internet-ip-webcam" title="link to 雕虫晓技(七) 用旧Android手机做远程摄像头">雕虫晓技(七) 用旧Android手机做远程摄像头</a></h2>
       <p class="excerpt">关于作者GcsSloop，一名 2.5 次元魔法师。微博 | GitHub | 博客0. 前言科技发展越来越快，手机也换的越来越频繁，有很多旧手机都被淘汰了，这些旧手机一般没什么用，也不能卖，毕...&hellip;</p>
       <div class="post-list__meta">
          <time datetime="2018-06-12 00:00:00 +0800" class="post-list__meta--date date">2018-06-12</time> &#8226; <span class="post-list__meta--tags tags">GeBug</span>
          <br/><br/>
          <a style="float:none; margin:0 auto;" class="btn-border-small" href=/gebug/internet-ip-webcam>继续阅读</a>
       </div>
   </div>
   
</section>

<!--网易云跟帖-->
<!--
<div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
<script src="https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js"></script>
<script>
var cloudTieConfig = {
  url: document.location.href, 
  sourceId: "",
  productKey: "a85dba2840134721a7b69a15b2e0f217",
  target: "cloud-tie-wrapper"
};
var yunManualLoad = true;
Tie.loader("aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s", true);
</script>
-->

<style type="text/css">
.isso-comment > div.avatar {
    border: 0px;
    box-shadow: none;
    display: block;
    float: left;
    width: 7%;
    margin: 3px 15px 0 0;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input {
    border-radius: 6px;
    padding: 6px;
    padding-left: 16px;
    padding-right: 16px;
    border: 1px solid #CCC;
    background-color: #D58D44;
    cursor: pointer;
    outline: 0;
    color: #fff;
    size: 10;
    line-height: 1.4em;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:hover {
    background-color: #272822;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
    background-color: #986530;
}
</style>

<section id="isso-thread"></section>

<script data-isso="//47.52.58.34:1234/"
        data-isso-css="true"
        data-isso-lang="zh"
        data-isso-reply-to-self="false"
        data-isso-require-author="false"
        data-isso-require-email="false"
        data-isso-max-comments-top="10"
        data-isso-max-comments-nested="5"
        data-isso-reveal-on-click="5"
        data-isso-avatar="true"
        data-isso-avatar-bg="#f0f0f0"
        data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
        data-isso-vote="true"
        data-vote-levels=""
        src="//47.52.58.34:1234/js/embed.min.js">
        </script>

<!--
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
-->
<!-- OneV's Den -->
<!--
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client="ca-pub-3324997515191619"
     data-ad-slot="9170309685"
     data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
-->

            <section class="footer">
    <footer>
    	<span class="footer__copyright">本站点采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh" target="_blank">知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议</a></span>
        <span class="footer__copyright">本站由 <a href="http://www.GcsSloop.com">@GcsSloop</a> 创建，采用 <a href="https://github.com/GcsSloop/Gcs-Vno-Jekyll" target="_blank">Gcs-Vno-Jekyll</a> 作为主题。<span id="busuanzi_container_site_pv"> 总访问量 <span id="busuanzi_value_site_pv"></span> 次</span> - &copy; 2019</span>
        <span class="footer__sitemap, footer__copyright"><a href="http://www.gcssloop.com/sitemap.xml" target="_blank">Site Map</a>
        <a href="http://www.gcssloop.com/vip" target="_blank">vip</a></span>
    </footer>
</section>

        </div>
    </div>
    
    <script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>

<script type="text/javascript" src="/js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

<script type="text/javascript" src="/js/main.js"></script>

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-82493667-1', 'auto');
  ga('send', 'pageview');

</script>

    
  </body>

</html>
